﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExcelAddIn1
{
    /**
     * Implementation of the Wilcoxon signed-rank test.
     */
    class Test_WilcoxonSignedRank : Test
    {
        private class RankItem : IComparable
        {
            public double val1;
            public double val2;
            public double D;
            public double absD;
            public double R;
            public double SR;
        
            public RankItem(double val1, double val2)
            {
                this.val1 = val1;
                this.val2 = val2;
                this.D = val1 - val2;
                this.absD = Math.Abs(D);
            }
        
            public int CompareTo(Object obj)
            {
                RankItem ri = (RankItem)obj;
                if (absD < ri.absD)
                {
                    return -1;
                }
                else if (absD > ri.absD)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }

        //Critical W table,alpha=0.05
        private double[] Wcrit = { 0, 2, 4, 6, 8, 11, 14, 17, 21, 25, 30, 35, 40, 46, 52, 59, 66, 73, 81, 89 };
        
        public Test_WilcoxonSignedRank(DataContainer data)
        {
            this.data = data;
            res = new List<String>();
            alpha = 0.05;
        }

        public override bool EnableAlphaField()
        {
            return false;
        }

        public override string GetInfo()
        {
            String s = "Performs a Wilcoxon signed-rank test to check for significant differences between two datasets. ";
            s += "The test does not require normally distributed data, but the datasets must be dependent.";
            return s;
        }

        public override void RunTest()
        {
            res.Add("Wilcoxon Signed Rank test");

            //Error check
            if (data.GetNoSets() != 2)
            {
                res.Add("Two datasets is required!");
                return;
            }

            //Get data sets
            DataSet d1 = data.GetDataSet(0);
            DataSet d2 = data.GetDataSet(1);
            int n = d1.GetN();

            //Error check 2
            if (d1.GetN() != d2.GetN())
            {
                res.Add("Both datasets must have equal sample sizes!");
                return;
            }
            //Error check 3
            if (n < 6)
            {
                res.Add("Sample size must be at least 6!");
                return;
            }
            //Error check 4
            if (n > 25)
            {
                res.Add("Sample size must be lower than 25!");
                return;
            }

            //Create the rankings table
            List<RankItem> rankTab = new List<RankItem>(n);
            //Fill table from the datasets
            for (int i = 0; i < d1.GetN(); i++)
            {
                //Do not add entries with zero difference.
                if (d1.GetValue(i) != d2.GetValue(i))
                {
                    rankTab.Add(new RankItem(d1.GetValue(i), d2.GetValue(i)));
                }
            }

            //Sort the table
            rankTab.Sort();
            //Fill in ranks
            for (int i = 0; i < rankTab.Count; i++)
            {
                rankTab[i].R = i + 1;
                rankTab[i].SR = i + 1;
                if (rankTab[i].D < 0)
                {
                    rankTab[i].SR = -(i + 1);
                }
            }
            //Fix rank duplicates
            double prev = rankTab[0].absD;
            double sumRank = rankTab[0].R;
            int cnt = 1;
            for (int i = 1; i < rankTab.Count; i++)
            {
                double iVal = rankTab[i].absD;
                if (iVal == prev)
                {
                    //Equal values. Find a mean rank.
                    sumRank += rankTab[i].R;
                    cnt++;
                }
                else
                {
                    if (cnt > 1)
                    {
                        //Duplicates found. Update rank to a mean rank.
                        double meanR = sumRank / (double)(cnt);
                        for (int d = i - 1; d >= i - cnt; d--)
                        {
                            rankTab[d].R = meanR;
                            rankTab[d].SR = meanR;
                            if (rankTab[d].D < 0)
                            {
                                rankTab[d].SR = -1.0 * meanR;
                            }
                        }
                    }
                    cnt = 1;
                    sumRank = rankTab[i].R;
                }
                prev = iVal;
            }
            //Rankings table done!

            //Calculate sum ranks
            double SRpos = 0.0;
            double SRneg = 0.0;
            for (int i = 0; i < rankTab.Count; i++)
            {
                double sr = rankTab[i].SR;
                if (sr > 0)
                {
                    SRpos += sr;
                }
                else
                {
                    SRneg += sr;
                }
            }

            //Calculate W-score
            double W = Math.Min(SRpos, Math.Abs(SRneg));

            //Find Critical W
            int index = n - 6;
            double Wc = (double)Wcrit[index];


            //Results
            res.Add(";" + d1.GetName() + ";" + d2.GetName() + ";-");
            res.Add("N;" + d1.GetN() + ";" + d2.GetN() + ";-");
            res.Add("Mean;" + d1.GetMean().ToString("F2") + ";" + d2.GetMean().ToString("F2"));
            res.Add("StDev;" + d1.GetStDev().ToString("F2") + ";" + d2.GetStDev().ToString("F2"));
            res.Add("α;" + alpha + ";;-");
            res.Add("W-score;" + W.ToString("F2"));
            res.Add("W-crit;" + Wc.ToString("F2"));
            if (W <= Wc)
            {
                res.Add("Result;Significant difference");
            }
            else
            {
                res.Add("Result;No difference");
            }
            res.Add(";;;-"); 
        }
    }
}
